@ampless/runtime 0.2.0-alpha.9 → 1.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +35 -9
- package/README.md +35 -9
- package/dist/dispatchers/index.d.ts +14 -23
- package/dist/dispatchers/index.js +7 -22
- package/dist/index.d.ts +35 -26
- package/dist/index.js +176 -96
- package/dist/middleware.d.ts +33 -21
- package/dist/middleware.js +181 -19
- package/dist/routes/index.d.ts +53 -80
- package/dist/routes/index.js +85 -79
- package/package.json +4 -3
package/README.ja.md
CHANGED
|
@@ -34,10 +34,10 @@ export const ampless = createAmpless({
|
|
|
34
34
|
})
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`app
|
|
37
|
+
`app/` 直下のルートとディスパッチャーはワンライナーのファクトリー呼び出しになります:
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
// app/
|
|
40
|
+
// app/page.tsx
|
|
41
41
|
import { ampless } from '@/lib/ampless'
|
|
42
42
|
import {
|
|
43
43
|
createThemeHomeDispatcher,
|
|
@@ -50,7 +50,7 @@ export default createThemeHomeDispatcher(ampless)
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
// app/
|
|
53
|
+
// app/og/[slug]/route.ts
|
|
54
54
|
import { ampless } from '@/lib/ampless'
|
|
55
55
|
import { createOgRouteHandler } from '@ampless/runtime/routes'
|
|
56
56
|
|
|
@@ -59,22 +59,48 @@ export const dynamic = 'force-dynamic'
|
|
|
59
59
|
export const GET = createOgRouteHandler(ampless)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
ミドルウェア(proxy):
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
|
-
// middleware.ts
|
|
65
|
+
// proxy.ts (Next.js 16 で middleware.ts から改名)
|
|
66
66
|
import cmsConfig from './cms.config'
|
|
67
|
-
import
|
|
67
|
+
import outputs from './amplify_outputs.json'
|
|
68
|
+
import { createAmplessMiddleware } from '@ampless/runtime/middleware'
|
|
68
69
|
|
|
69
|
-
export const
|
|
70
|
-
|
|
70
|
+
export const proxy = createAmplessMiddleware({
|
|
71
|
+
cmsConfig,
|
|
72
|
+
appsyncUrl: outputs.data.url,
|
|
73
|
+
apiKey: outputs.data.api_key!,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// matcher はインライン定義 — Next.js 16 の Turbopack は `export const config`
|
|
77
|
+
// が静的オブジェクトリテラルであることを要求します。
|
|
78
|
+
export const config = {
|
|
79
|
+
matcher: [
|
|
80
|
+
'/((?!admin|api|login|_next/static|_next/image|favicon\\.ico|amplify_outputs\\.json).*)',
|
|
81
|
+
],
|
|
82
|
+
}
|
|
71
83
|
```
|
|
72
84
|
|
|
85
|
+
middleware はリクエストごとに AppSync から `post.format` /
|
|
86
|
+
`post.metadata` / `post.updatedAt` を取得(apiKey 認証)し、slug をキー
|
|
87
|
+
にした 200 エントリの LRU(60 秒 TTL、Lambda モジュールスコープ)で
|
|
88
|
+
キャッシュした上で、適切な内部ハンドラーにリクエストを書き換えます。
|
|
89
|
+
|
|
90
|
+
- 通常の投稿 → 書き換えなし、`app/[slug]/page.tsx` で配信
|
|
91
|
+
- `metadata.no_layout: true` HTML / `format: 'static'` → `/r/<slug>(/<path>)`、
|
|
92
|
+
`app/r/[slug]/[[...path]]/route.ts` で配信
|
|
93
|
+
|
|
94
|
+
また、`post.metadata.cache`(auto / deep / hot)+ `post.updatedAt` +
|
|
95
|
+
`cms.config.cache.{cooldownMs, freshTtlSeconds, deepTtlSeconds}` から
|
|
96
|
+
`Cache-Control` を算出してレスポンスに付与します。キャッシュ戦略の
|
|
97
|
+
契約については `docs/CONTENT.md` を参照してください。
|
|
98
|
+
|
|
73
99
|
## サブパス
|
|
74
100
|
|
|
75
101
|
- `@ampless/runtime` — `createAmpless`、ランタイム型、`renderBody`・`renderThemeCss`・フォーマットコンバーターの再エクスポート
|
|
76
102
|
- `@ampless/runtime/middleware` — `createAmplessMiddleware`、`defaultMatcherConfig`
|
|
77
|
-
- `@ampless/runtime/routes` — `createOgRouteHandler`、`createSitemapRouteHandler`、`createFeedRouteHandler`、`
|
|
103
|
+
- `@ampless/runtime/routes` — `createOgRouteHandler`、`createSitemapRouteHandler`、`createFeedRouteHandler`、`createUnderscoreRouteHandler`
|
|
78
104
|
- `@ampless/runtime/dispatchers` — `createThemeHomeDispatcher`、`createThemePostDispatcher`、`createThemeTagDispatcher`(それぞれ対応する `*Metadata` ファクトリーあり)
|
|
79
105
|
|
|
80
106
|
## テンプレートに残るもの
|
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ export const ampless = createAmpless({
|
|
|
34
34
|
})
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Routes and dispatchers under `app
|
|
37
|
+
Routes and dispatchers under `app/` become one-line factory invocations:
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
// app/
|
|
40
|
+
// app/page.tsx
|
|
41
41
|
import { ampless } from '@/lib/ampless'
|
|
42
42
|
import {
|
|
43
43
|
createThemeHomeDispatcher,
|
|
@@ -50,7 +50,7 @@ export default createThemeHomeDispatcher(ampless)
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
// app/
|
|
53
|
+
// app/og/[slug]/route.ts
|
|
54
54
|
import { ampless } from '@/lib/ampless'
|
|
55
55
|
import { createOgRouteHandler } from '@ampless/runtime/routes'
|
|
56
56
|
|
|
@@ -59,22 +59,48 @@ export const dynamic = 'force-dynamic'
|
|
|
59
59
|
export const GET = createOgRouteHandler(ampless)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Middleware:
|
|
62
|
+
Middleware (proxy):
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
|
-
// middleware.ts
|
|
65
|
+
// proxy.ts (Next.js 16 rename of middleware.ts)
|
|
66
66
|
import cmsConfig from './cms.config'
|
|
67
|
-
import
|
|
67
|
+
import outputs from './amplify_outputs.json'
|
|
68
|
+
import { createAmplessMiddleware } from '@ampless/runtime/middleware'
|
|
68
69
|
|
|
69
|
-
export const
|
|
70
|
-
|
|
70
|
+
export const proxy = createAmplessMiddleware({
|
|
71
|
+
cmsConfig,
|
|
72
|
+
appsyncUrl: outputs.data.url,
|
|
73
|
+
apiKey: outputs.data.api_key!,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Inline the matcher — Next.js 16's Turbopack requires
|
|
77
|
+
// `export const config` to be a static object literal.
|
|
78
|
+
export const config = {
|
|
79
|
+
matcher: [
|
|
80
|
+
'/((?!admin|api|login|_next/static|_next/image|favicon\\.ico|amplify_outputs\\.json).*)',
|
|
81
|
+
],
|
|
82
|
+
}
|
|
71
83
|
```
|
|
72
84
|
|
|
85
|
+
The middleware fetches `post.format` / `post.metadata` / `post.updatedAt`
|
|
86
|
+
from AppSync (apiKey auth) on each request, caches the result in a
|
|
87
|
+
200-entry LRU keyed by slug (60-second TTL, Lambda module scope), and
|
|
88
|
+
rewrites the request to the right internal handler:
|
|
89
|
+
|
|
90
|
+
- themed post → no rewrite, served by `app/[slug]/page.tsx`
|
|
91
|
+
- `metadata.no_layout: true` HTML / `format: 'static'` → `/r/<slug>(/<path>)`,
|
|
92
|
+
served by `app/r/[slug]/[[...path]]/route.ts`
|
|
93
|
+
|
|
94
|
+
It also computes `Cache-Control` from `post.metadata.cache` (auto /
|
|
95
|
+
deep / hot) + `post.updatedAt` + `cms.config.cache.{cooldownMs,
|
|
96
|
+
freshTtlSeconds, deepTtlSeconds}` and sets the header on the response.
|
|
97
|
+
See `docs/CONTENT.md` for the cache strategy contract.
|
|
98
|
+
|
|
73
99
|
## Sub-paths
|
|
74
100
|
|
|
75
101
|
- `@ampless/runtime` — `createAmpless`, runtime types, and re-exports of `renderBody`, `renderThemeCss`, format converters
|
|
76
102
|
- `@ampless/runtime/middleware` — `createAmplessMiddleware`, `defaultMatcherConfig`
|
|
77
|
-
- `@ampless/runtime/routes` — `createOgRouteHandler`, `createSitemapRouteHandler`, `createFeedRouteHandler`, `
|
|
103
|
+
- `@ampless/runtime/routes` — `createOgRouteHandler`, `createSitemapRouteHandler`, `createFeedRouteHandler`, `createUnderscoreRouteHandler`
|
|
78
104
|
- `@ampless/runtime/dispatchers` — `createThemeHomeDispatcher`, `createThemePostDispatcher`, `createThemeTagDispatcher` (each with a matching `*Metadata` factory)
|
|
79
105
|
|
|
80
106
|
## What's still in the template
|
|
@@ -4,16 +4,14 @@ import { Ampless } from '../index.js';
|
|
|
4
4
|
import 'ampless';
|
|
5
5
|
|
|
6
6
|
interface Props$2 {
|
|
7
|
-
params: Promise<
|
|
8
|
-
siteId: string;
|
|
9
|
-
}>;
|
|
7
|
+
params: Promise<Record<string, never>>;
|
|
10
8
|
}
|
|
11
9
|
type ThemeHomeDispatcher = (props: Props$2) => Promise<ReactNode>;
|
|
12
10
|
type ThemeHomeMetadata = (props: Props$2) => Promise<Metadata>;
|
|
13
11
|
/**
|
|
14
|
-
* Home page dispatcher. Resolves the active theme
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Home page dispatcher. Resolves the active theme and renders the
|
|
13
|
+
* theme's `components.Home` server component with the same `params`
|
|
14
|
+
* Promise it was passed.
|
|
17
15
|
*/
|
|
18
16
|
declare function createThemeHomeDispatcher(ampless: Ampless): ThemeHomeDispatcher;
|
|
19
17
|
/** generateMetadata factory for the home dispatcher. */
|
|
@@ -21,29 +19,23 @@ declare function createThemeHomeMetadata(ampless: Ampless): ThemeHomeMetadata;
|
|
|
21
19
|
|
|
22
20
|
interface Props$1 {
|
|
23
21
|
params: Promise<{
|
|
24
|
-
siteId: string;
|
|
25
22
|
slug: string;
|
|
26
23
|
}>;
|
|
27
24
|
}
|
|
28
25
|
type ThemePostDispatcher = (props: Props$1) => Promise<ReactNode>;
|
|
29
26
|
type ThemePostMetadata = (props: Props$1) => Promise<Metadata>;
|
|
30
27
|
/**
|
|
31
|
-
*
|
|
32
|
-
* theme's `components.Post` server component.
|
|
33
|
-
*
|
|
34
|
-
* Before delegating, the dispatcher peeks at the post's metadata: if
|
|
35
|
-
* `metadata.no_layout === true`, the post is meant to be served as
|
|
36
|
-
* bare HTML (own DOCTYPE, no theme chrome, no Next.js root layout).
|
|
37
|
-
* Next.js page.tsx can't bypass the root layout, so we 308-redirect
|
|
38
|
-
* to the raw route handler at `/raw/<slug>` which returns the body
|
|
39
|
-
* unchanged. The redirect is permanent because no_layout is a
|
|
40
|
-
* persistent property of the post — bookmarks naturally settle on
|
|
41
|
-
* the `/raw/` URL.
|
|
28
|
+
* Themed post page dispatcher. Resolves the active theme and renders
|
|
29
|
+
* the theme's `components.Post` server component.
|
|
42
30
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
31
|
+
* Routing decision (no_layout HTML vs static bundle vs themed) lives
|
|
32
|
+
* in middleware now — middleware fetches `post.format` /
|
|
33
|
+
* `post.metadata` / `post.updatedAt` from AppSync once per slug
|
|
34
|
+
* (Lambda-memory LRU, 60s TTL) and rewrites the request to either
|
|
35
|
+
* this dispatcher (themed render) or the unified route handler at
|
|
36
|
+
* `/r/<slug>(/...)` (no_layout HTML or static bundle). That keeps the
|
|
37
|
+
* decision in one place and avoids the dispatcher's extra AppSync
|
|
38
|
+
* round-trip when the post is a themed render.
|
|
47
39
|
*
|
|
48
40
|
* If the theme doesn't declare a Post component at all, returns
|
|
49
41
|
* Next.js's notFound() (404).
|
|
@@ -54,7 +46,6 @@ declare function createThemePostMetadata(ampless: Ampless): ThemePostMetadata;
|
|
|
54
46
|
|
|
55
47
|
interface Props {
|
|
56
48
|
params: Promise<{
|
|
57
|
-
siteId: string;
|
|
58
49
|
tag: string;
|
|
59
50
|
}>;
|
|
60
51
|
}
|
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
// src/dispatchers/home.ts
|
|
2
2
|
function createThemeHomeDispatcher(ampless) {
|
|
3
3
|
return async function SiteHomeDispatcher({ params }) {
|
|
4
|
-
const {
|
|
5
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
4
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
6
5
|
const Home = module.components.Home;
|
|
7
6
|
return await Home({ params });
|
|
8
7
|
};
|
|
9
8
|
}
|
|
10
9
|
function createThemeHomeMetadata(ampless) {
|
|
11
10
|
return async function generateMetadata({ params }) {
|
|
12
|
-
const {
|
|
13
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
11
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
14
12
|
const fn = module.metadata?.Home;
|
|
15
13
|
return fn ? await fn({ params }) : {};
|
|
16
14
|
};
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
// src/dispatchers/post.ts
|
|
20
|
-
import { notFound
|
|
18
|
+
import { notFound } from "next/navigation";
|
|
21
19
|
function createThemePostDispatcher(ampless) {
|
|
22
20
|
return async function SitePostDispatcher({ params }) {
|
|
23
|
-
const {
|
|
24
|
-
const post = await ampless.getPublishedPost(slug, { siteId });
|
|
25
|
-
if (post?.metadata?.no_layout === true) {
|
|
26
|
-
redirect(`/raw/${slug}`);
|
|
27
|
-
}
|
|
28
|
-
if (post?.format === "static") {
|
|
29
|
-
const body = post.body ?? null;
|
|
30
|
-
const entrypoint = typeof body?.entrypoint === "string" && body.entrypoint ? body.entrypoint : "index.html";
|
|
31
|
-
redirect(`/${slug}/${entrypoint}`);
|
|
32
|
-
}
|
|
33
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
21
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
34
22
|
const Post = module.components.Post;
|
|
35
23
|
if (!Post) notFound();
|
|
36
24
|
return await Post({ params });
|
|
@@ -38,8 +26,7 @@ function createThemePostDispatcher(ampless) {
|
|
|
38
26
|
}
|
|
39
27
|
function createThemePostMetadata(ampless) {
|
|
40
28
|
return async function generateMetadata({ params }) {
|
|
41
|
-
const {
|
|
42
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
29
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
43
30
|
const fn = module.metadata?.Post;
|
|
44
31
|
return fn ? await fn({ params }) : {};
|
|
45
32
|
};
|
|
@@ -49,8 +36,7 @@ function createThemePostMetadata(ampless) {
|
|
|
49
36
|
import { notFound as notFound2 } from "next/navigation";
|
|
50
37
|
function createThemeTagDispatcher(ampless) {
|
|
51
38
|
return async function SiteTagDispatcher({ params }) {
|
|
52
|
-
const {
|
|
53
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
39
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
54
40
|
const Tag = module.components.Tag;
|
|
55
41
|
if (!Tag) notFound2();
|
|
56
42
|
return await Tag({ params });
|
|
@@ -58,8 +44,7 @@ function createThemeTagDispatcher(ampless) {
|
|
|
58
44
|
}
|
|
59
45
|
function createThemeTagMetadata(ampless) {
|
|
60
46
|
return async function generateMetadata({ params }) {
|
|
61
|
-
const {
|
|
62
|
-
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
47
|
+
const { module } = await ampless.resolveActiveTheme();
|
|
63
48
|
const fn = module.metadata?.Tag;
|
|
64
49
|
return fn ? await fn({ params }) : {};
|
|
65
50
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,6 @@ interface StorageApi {
|
|
|
25
25
|
|
|
26
26
|
interface PublicPostShape {
|
|
27
27
|
postId: string;
|
|
28
|
-
siteId: string;
|
|
29
28
|
slug: string;
|
|
30
29
|
title: string;
|
|
31
30
|
excerpt?: string | null;
|
|
@@ -35,13 +34,18 @@ interface PublicPostShape {
|
|
|
35
34
|
publishedAt?: string | null;
|
|
36
35
|
tags?: Array<string | null> | null;
|
|
37
36
|
metadata?: unknown;
|
|
37
|
+
/**
|
|
38
|
+
* ISO 8601 timestamp from DynamoDB's auto-managed `updatedAt`.
|
|
39
|
+
* Surfaced through the `PublicPost` projection in v0.2 alpha so
|
|
40
|
+
* middleware can compute the `metadata.cache='auto'` cooldown.
|
|
41
|
+
*/
|
|
42
|
+
updatedAt?: string | null;
|
|
38
43
|
}
|
|
39
44
|
interface PublicPostConnectionShape {
|
|
40
45
|
items?: Array<PublicPostShape | null> | null;
|
|
41
46
|
nextToken?: string | null;
|
|
42
47
|
}
|
|
43
48
|
interface ListPostsOptions {
|
|
44
|
-
siteId?: string;
|
|
45
49
|
/** ISO 8601 timestamp; SK lower bound (inclusive). */
|
|
46
50
|
from?: string;
|
|
47
51
|
/** ISO 8601 timestamp; SK upper bound (inclusive). */
|
|
@@ -51,7 +55,6 @@ interface ListPostsOptions {
|
|
|
51
55
|
nextToken?: string;
|
|
52
56
|
}
|
|
53
57
|
interface ListPostsByTagOptions {
|
|
54
|
-
siteId?: string;
|
|
55
58
|
limit?: number;
|
|
56
59
|
nextToken?: string;
|
|
57
60
|
}
|
|
@@ -61,9 +64,7 @@ interface ListPostsResult {
|
|
|
61
64
|
}
|
|
62
65
|
interface PostsApi {
|
|
63
66
|
listPublishedPosts(opts?: ListPostsOptions): Promise<ListPostsResult>;
|
|
64
|
-
getPublishedPost(slug: string
|
|
65
|
-
siteId?: string;
|
|
66
|
-
}): Promise<Post | null>;
|
|
67
|
+
getPublishedPost(slug: string): Promise<Post | null>;
|
|
67
68
|
listPostsByTag(tag: string, opts?: ListPostsByTagOptions): Promise<ListPostsResult>;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -87,18 +88,18 @@ interface EffectiveSiteSettings {
|
|
|
87
88
|
timezone?: string;
|
|
88
89
|
}
|
|
89
90
|
interface SiteSettingsApi {
|
|
90
|
-
loadSiteSettings(
|
|
91
|
+
loadSiteSettings(): Promise<EffectiveSiteSettings>;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
interface SeoApi {
|
|
94
|
-
postMetadata(post: Post
|
|
95
|
-
siteMetadata(
|
|
95
|
+
postMetadata(post: Post): Promise<Metadata>;
|
|
96
|
+
siteMetadata(): Promise<Metadata>;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
interface ThemesRegistry {
|
|
99
100
|
/** Map of theme name → loaded theme module. */
|
|
100
101
|
themes: Record<string, ThemeModule>;
|
|
101
|
-
/** Name used when no `theme.active` override is stored
|
|
102
|
+
/** Name used when no `theme.active` override is stored. */
|
|
102
103
|
defaultTheme: string;
|
|
103
104
|
}
|
|
104
105
|
interface ResolvedTheme {
|
|
@@ -106,7 +107,7 @@ interface ResolvedTheme {
|
|
|
106
107
|
module: ThemeModule;
|
|
107
108
|
}
|
|
108
109
|
interface ThemeActiveApi {
|
|
109
|
-
resolveActiveTheme(
|
|
110
|
+
resolveActiveTheme(): Promise<ResolvedTheme>;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
type ColorScheme = 'auto' | 'light' | 'dark';
|
|
@@ -145,7 +146,7 @@ interface EffectiveThemeConfig {
|
|
|
145
146
|
colorScheme: ColorScheme;
|
|
146
147
|
}
|
|
147
148
|
interface ThemeConfigApi {
|
|
148
|
-
loadThemeConfig(
|
|
149
|
+
loadThemeConfig(): Promise<EffectiveThemeConfig>;
|
|
149
150
|
}
|
|
150
151
|
/**
|
|
151
152
|
* Render `cssVars` as the body of a `:root { ... }` CSS block. Values
|
|
@@ -159,32 +160,42 @@ declare function renderBody(post: Post): string;
|
|
|
159
160
|
* Convert a tiptap doc to its HTML form. Same renderer the public
|
|
160
161
|
* site uses. Defensive: tiptap accepts an HTML string as initial
|
|
161
162
|
* content and parses it on mount, but won't fire onUpdate until the
|
|
162
|
-
* user edits
|
|
163
|
+
* user edits, so a format-switch chain (e.g. markdown -> tiptap ->
|
|
163
164
|
* markdown without editing) can still hand us a raw HTML string
|
|
164
165
|
* here. In that case, return it as-is rather than walking it as a
|
|
165
166
|
* malformed tiptap node and producing empty output.
|
|
166
167
|
*/
|
|
167
168
|
declare function tiptapToHtml(doc: unknown): string;
|
|
168
|
-
/** Convert markdown to HTML using
|
|
169
|
+
/** Convert markdown to HTML using marked + GFM. */
|
|
169
170
|
declare function markdownToHtml(md: string): string;
|
|
170
171
|
/**
|
|
171
172
|
* Walk a tiptap doc and emit Markdown. Mirrors `renderTiptap` in
|
|
172
173
|
* shape but produces markdown syntax. Loses anything markdown can't
|
|
173
174
|
* express (data attributes, image display modes, custom marks).
|
|
174
175
|
*
|
|
176
|
+
* Notes on info loss:
|
|
177
|
+
* - underline / highlight are not in GFM, so they fall back to the
|
|
178
|
+
* literal `<u>` / `<mark>` HTML tags (preserved as-is across round trips).
|
|
179
|
+
* - paragraph / heading textAlign cannot be expressed in markdown and
|
|
180
|
+
* is therefore lost on conversion.
|
|
181
|
+
*
|
|
175
182
|
* Same defensive path as tiptapToHtml: a string input means tiptap
|
|
176
183
|
* hasn't emitted JSON yet (the body is still the HTML we handed it).
|
|
177
184
|
* Route through htmlToMarkdown so the content survives.
|
|
178
185
|
*/
|
|
179
186
|
declare function tiptapToMarkdown(doc: unknown): string;
|
|
180
187
|
/**
|
|
181
|
-
* Regex-based HTML
|
|
188
|
+
* Regex-based HTML -> Markdown converter. Handles the tag set the
|
|
182
189
|
* editor produces (`<p>` `<h1>`-`<h6>` `<strong>` `<em>` `<a>`
|
|
183
190
|
* `<img>` `<ul>` `<ol>` `<li>` `<code>` `<pre>` `<blockquote>` `<hr>`
|
|
184
|
-
* `<br>`
|
|
185
|
-
*
|
|
191
|
+
* `<br>` `<u>` `<mark>` `<table>` task-list `<ul data-type="taskList">`).
|
|
192
|
+
* Decorative containers like `<div style="text-align:...">` are dropped.
|
|
193
|
+
*
|
|
194
|
+
* Tables are reduced to GFM pipe syntax via convertHtmlTable. Complex
|
|
195
|
+
* nested content inside cells (lists, other tables) is flattened to
|
|
196
|
+
* plain text.
|
|
186
197
|
*
|
|
187
|
-
* Not a full library
|
|
198
|
+
* Not a full library, there are known limits like nested formatting
|
|
188
199
|
* inside list items potentially merging. Acceptable for a v0.x
|
|
189
200
|
* format-switch convenience; complex HTML round-trips shouldn't be
|
|
190
201
|
* relied on.
|
|
@@ -198,15 +209,13 @@ interface CreateAmplessOpts {
|
|
|
198
209
|
}
|
|
199
210
|
interface Ampless {
|
|
200
211
|
listPublishedPosts(opts?: ListPostsOptions): Promise<ListPostsResult>;
|
|
201
|
-
getPublishedPost(slug: string
|
|
202
|
-
siteId?: string;
|
|
203
|
-
}): Promise<Post | null>;
|
|
212
|
+
getPublishedPost(slug: string): Promise<Post | null>;
|
|
204
213
|
listPostsByTag(tag: string, opts?: ListPostsByTagOptions): Promise<ListPostsResult>;
|
|
205
|
-
loadSiteSettings(
|
|
206
|
-
resolveActiveTheme(
|
|
207
|
-
loadThemeConfig(
|
|
208
|
-
postMetadata(post: Post
|
|
209
|
-
siteMetadata(
|
|
214
|
+
loadSiteSettings(): Promise<EffectiveSiteSettings>;
|
|
215
|
+
resolveActiveTheme(): Promise<ResolvedTheme>;
|
|
216
|
+
loadThemeConfig(): Promise<EffectiveThemeConfig>;
|
|
217
|
+
postMetadata(post: Post): Promise<Metadata>;
|
|
218
|
+
siteMetadata(): Promise<Metadata>;
|
|
210
219
|
renderBody(post: Post): string;
|
|
211
220
|
renderThemeCss(cssVars: Record<string, string>): string;
|
|
212
221
|
publicAssetUrl(key: string): string;
|